# SPDX-License-Identifier: MIT
"""Command-line interface for pmake."""

from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path
from typing import Iterable, Sequence

from .generator import generate
from .model import Manifest
from .parser import ManifestError, parse_manifest
from .state import load_disabled, save_disabled

DEFAULT_MANIFEST = "PMakeLists.txt"
DEFAULT_SKILLS_DIR = Path("/home/linden/claude/zclaude/skills")


def main(argv: Sequence[str] | None = None) -> int:
    parser = _build_arg_parser()
    args = parser.parse_args(argv)

    try:
        if args.command == "configure":
            return _cmd_configure(args)
        if args.command == "disable":
            return _cmd_disable(args)
        if args.command == "info":
            return _cmd_info(args)
        if args.command == "help":
            return _cmd_help(args)
        if args.command == "skills-list":
            return _cmd_skills_list(args)
        parser.error(f"Unknown command {args.command!r}")
    except ManifestError as exc:
        print(f"pmake: manifest error: {exc}", file=sys.stderr)
        return 2
    except RuntimeError as exc:
        print(f"pmake: {exc}", file=sys.stderr)
        return 1
    return 0


def _build_arg_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(prog="pmake", description="Agent build orchestration")
    subparsers = parser.add_subparsers(dest="command", required=True)

    p_configure = subparsers.add_parser("configure", help="Generate build artifacts from manifest")
    p_configure.add_argument(
        "--manifest",
        type=Path,
        default=Path(DEFAULT_MANIFEST),
        help=f"Path to manifest (default: {DEFAULT_MANIFEST})",
    )

    p_disable = subparsers.add_parser("disable", help="Disable optional targets")
    p_disable.add_argument(
        "targets",
        nargs="+",
        help="Optional target names to disable",
    )
    p_disable.add_argument(
        "--manifest",
        type=Path,
        default=Path(DEFAULT_MANIFEST),
        help=f"Path to manifest (default: {DEFAULT_MANIFEST})",
    )

    p_skills = subparsers.add_parser("skills-list", help="List available skill prompts")
    p_skills.add_argument(
        "--skills-dir",
        type=Path,
        default=DEFAULT_SKILLS_DIR,
        help=f"Skills directory (default: {DEFAULT_SKILLS_DIR})",
    )

    p_info = subparsers.add_parser("info", help="Print implementation records for targets")
    p_info.add_argument(
        "--manifest",
        type=Path,
        default=Path(DEFAULT_MANIFEST),
        help=f"Path to manifest (default: {DEFAULT_MANIFEST})",
    )

    p_help = subparsers.add_parser("help", help="List targets and helper commands")
    p_help.add_argument(
        "--manifest",
        type=Path,
        default=Path(DEFAULT_MANIFEST),
        help=f"Path to manifest (default: {DEFAULT_MANIFEST})",
    )

    return parser


def _cmd_configure(args: argparse.Namespace) -> int:
    manifest_path: Path = args.manifest.resolve()
    manifest = _load_manifest(manifest_path)
    disabled = load_disabled(manifest.manifest_path.parent / ".pmake")
    generate(manifest, disabled=disabled)
    print(f"pmake: configured build at {manifest.manifest_path.parent / '.pmake'}")
    print("       run `make -C .pmake` to execute targets")
    return 0


def _cmd_disable(args: argparse.Namespace) -> int:
    manifest_path: Path = args.manifest.resolve()
    manifest = _load_manifest(manifest_path)
    build_dir = manifest_path.parent / ".pmake"
    disabled = load_disabled(build_dir)
    for name in args.targets:
        if name not in manifest.targets:
            raise RuntimeError(f"unknown target {name!r}")
        target = manifest.targets[name]
        if target.is_essential():
            raise RuntimeError(f"cannot disable essential target {name!r}")
        disabled.add(name)
    save_disabled(build_dir, disabled)
    generate(manifest, disabled=disabled)
    print(f"pmake: disabled targets {', '.join(sorted(disabled))}")
    return 0


def _cmd_info(args: argparse.Namespace) -> int:
    manifest_path: Path = args.manifest.resolve()
    manifest = _load_manifest(manifest_path)
    build_dir = manifest_path.parent / ".pmake"
    meta_dir = build_dir / "meta"
    disabled = load_disabled(build_dir)

    if not meta_dir.exists():
        raise RuntimeError("metadata missing; run `pmake configure` first")

    print(f"Project: {manifest.project.name} (version {manifest.project.version or 'n/a'})")
    print(f"Manifest: {manifest.manifest_path}")
    if manifest.project.globals:
        print("Globals:")
        for path in manifest.project.globals:
            print(f"  - {path}")
    else:
        print("Globals: (none)")

    print("\nTargets:")
    for name, target in manifest.targets.items():
        if name in disabled:
            print(f"- {name} ({target.kind}) [disabled]")
            continue
        meta_path = meta_dir / f"{name}.json"
        if not meta_path.exists():
            print(f"- {name} ({target.kind}) [pending configure]")
            continue
        meta = _load_meta(meta_path)
        status, stamp = _determine_status(meta)
        deps = meta.get("deps", {})
        skill_deps = ", ".join(deps.get("skills", [])) or "(none)"
        target_deps = ", ".join(deps.get("targets", [])) or "(none)"
        extern_deps = ", ".join(deps.get("extern", [])) or "(none)"
        worklog_path = Path(meta["paths"]["worklog"])
        error_path = Path(meta["paths"]["error"])
        last_attempt = _last_attempt_entry(worklog_path) or _last_attempt_entry(error_path)
        worklog_state = "present" if worklog_path.exists() else "missing"
        err_state = "present" if error_path.exists() else "missing"

        print(f"- {name} ({target.kind})")
        print(f"    Status    : {status}{f' @ {stamp}' if stamp else ''}")
        print(f"    Executor  : {meta.get('executor')}")
        print(f"    Depends   : targets={target_deps}; extern={extern_deps}; skills={skill_deps}")
        print(f"    Worklog   : {worklog_path} [{worklog_state}]")
        print(f"    Err Log   : {error_path} [{err_state}]")
        if last_attempt:
            print(f"    Last entry: {last_attempt}")

    if disabled:
        print("\nDisabled optional targets:")
        for name in sorted(disabled):
            print(f"  - {name}")
    return 0


def _cmd_skills_list(args: argparse.Namespace) -> int:
    skills_dir: Path = args.skills_dir.expanduser().resolve()
    if not skills_dir.exists():
        print(f"pmake: skills directory not found: {skills_dir}", file=sys.stderr)
        return 1
    entries = sorted(skills_dir.iterdir())
    if not entries:
        print("pmake: no skills found")
        return 0
    for item in entries:
        if item.name.startswith("."):
            continue
        if item.is_dir():
            print(f"skill://{item.name}/")
        elif item.is_file():
            print(f"skill://{item.stem}")
    return 0


def _load_manifest(path: Path) -> Manifest:
    if not path.exists():
        raise RuntimeError(f"manifest not found at {path}")
    manifest = parse_manifest(path)
    return manifest


def _cmd_help(args: argparse.Namespace) -> int:
    manifest_path: Path = args.manifest.resolve()
    manifest = _load_manifest(manifest_path)
    build_dir = manifest_path.parent / ".pmake"
    disabled = load_disabled(build_dir)

    print("PMake helper commands:")
    print("  python -m pmake configure   # regenerate .pmake artifacts")
    print("  python -m pmake info        # show target statuses and logs")
    print("  python -m pmake skills-list # list available skills")
    print("  python -m pmake disable <target>  # disable optional target(s)")
    print("\nMake shortcuts:")
    print("  make -C .pmake              # run essential targets")
    print("  make -C .pmake optional     # run optional targets")
    print("  make -C .pmake info         # report status")
    print("  make -C .pmake help         # this help output")

    print("\nTargets:")
    for name, target in manifest.targets.items():
        status = []
        if name in disabled:
            status.append("disabled")
        status_text = f" [{' '.join(status)}]" if status else ""
        deps = ", ".join(target.deps) if target.deps else "(none)"
        print(f"  - {name} ({target.kind}){status_text}")
        print(f"       deps: {deps}")
    return 0


def _load_meta(path: Path) -> dict:
    try:
        return json.loads(path.read_text(encoding="utf-8"))
    except json.JSONDecodeError as exc:
        raise RuntimeError(f"invalid metadata at {path}: {exc}") from exc


def _determine_status(meta: dict) -> tuple[str, str | None]:
    state_ok = Path(meta["paths"]["state_ok"])
    state_fail = Path(meta["paths"]["state_fail"])
    if state_ok.exists():
        stamp = state_ok.read_text(encoding="utf-8").strip()
        return "success", stamp or None
    if state_fail.exists():
        stamp = state_fail.read_text(encoding="utf-8").strip()
        return "failure", stamp or None
    return "pending", None


def _last_attempt_entry(path: Path) -> str | None:
    if not path.exists():
        return None
    last = None
    try:
        for line in path.read_text(encoding="utf-8").splitlines():
            if line.startswith("## Attempt at "):
                last = line.replace("## Attempt at ", "", 1)
    except OSError:
        return None
    return last


if __name__ == "__main__":
    sys.exit(main())
